[BugFix] Handle opaque NullPointerException for unresolvable alias-type field path#5536
Conversation
When a mapping contains a field of "type": "alias" whose "path" points to a target absent from the flattened mapping (a text multi-field such as field.keyword, or a removed/renamed field), validateAliasType passed a null target into the OpenSearchAliasType constructor, which dereferenced it at super(type.getExprCoreType()) and surfaced an opaque NullPointerException. Guard the null target and throw a SemanticCheckException naming the alias field and its unresolved path. SemanticCheckException extends QueryEngineException, so JdbcResponseFormatter maps it to HTTP 400 (client error) rather than the misleading 500 a generic exception would produce. Add unit tests covering the .keyword multi-field and missing-field cases. Fixes opensearch-project#5535 Signed-off-by: Jialiang Liang <jiallian@amazon.com>
PR Reviewer Guide 🔍(Review updated until commit 9295c92)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 9295c92 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit 0d76d1c
Suggestions up to commit e533a78
|
Add a QueryValidationIT case asserting that SELECT * over an index whose alias field targets a text multi-field (source.keyword) returns a 400 SemanticCheckException with the descriptive message. An alias pointing at a truly missing field is rejected by OpenSearch at index-creation time, so it is not reachable through the SQL plugin and is covered by the unit test only. Shorten the unit-test comments and drop inline issue references. Signed-off-by: Jialiang Liang <jiallian@amazon.com>
|
Persistent review updated to latest commit 0d76d1c |
Signed-off-by: Jialiang Liang <jiallian@amazon.com>
|
Persistent review updated to latest commit 4d1c8e3 |
Wrap the SemanticCheckException in an ErrorReport (the report-builder interface from opensearch-project#5266) so the error carries structured context as it bubbles up: FIELD_NOT_FOUND code, ANALYZING stage, a location chain, the alias field and path as context, and a fix suggestion. On the PPL/Calcite path this renders as a rich structured error; on the SQL JDBC path it still returns a clear 400 (RestSqlAction unwraps to the SemanticCheckException cause), though the JdbcResponseFormatter does not yet render the ErrorReport structure. Update the unit test to assert the ErrorReport code/stage/context/cause, the SQL IT for the ErrorReport type, and add a PPL IT asserting the structured FIELD_NOT_FOUND error in CalciteErrorReportStageIT. Signed-off-by: Jialiang Liang <jiallian@amazon.com>
|
Persistent review updated to latest commit 9295c92 |
Description
When a mapping contains a field of
"type": "alias"whose"path"points to a target that is absent from the flattened mapping — for example a text multi-field (field.keyword) or a removed/renamed field —OpenSearchDataType.validateAliasType()passed anulltarget into theOpenSearchAliasTypeconstructor, which immediately dereferenced it atsuper(type.getExprCoreType()). The result was an opaqueNullPointerExceptionsurfaced to the user with no indication that the real problem is a malformed alias in their mapping:{ "error": { "reason": "Invalid SQL query", "details": "Cannot invoke \"org.opensearch.sql.opensearch.data.type.OpenSearchDataType.getExprCoreType()\" because \"type\" is null", "type": "NullPointerException" }, "status": 400 }Root cause:
traverseAndFlattenonly expands object/nestedpropertiesinto dotted keys; it does not add text multi-fields (.keyword), which are stored underOpenSearchTextType.fields. So an alias whosepathtargets such a field (or a field that no longer exists) resolves tonull. TheOpenSearchAliasTypeconstructor already guards the alias-to-alias and alias-to-object cases, but those checks run after thesuper()dereference, so the unresolvable-path case fell through to the NPE.Fix: Guard the null target in
validateAliasTypeand throw aSemanticCheckExceptionthat names the offending alias field and its unresolved path.SemanticCheckException extends QueryEngineException, whichJdbcResponseFormatter#getStatusmaps to HTTP 400 — an invalid alias mapping is a client error, so this also avoids the misleading HTTP 500 ("internal problem at backend") that a generic exception type would have produced.After the fix:
{ "error": { "reason": "Invalid SQL query", "details": "Alias field [source_alias] refers to unresolved path [source.keyword]. The alias path must point to an existing field in the mapping; a text multi-field (e.g. \"source.keyword.keyword\") or a removed/renamed field is not a valid alias target.", "type": "SemanticCheckException" }, "status": 400 }Valid aliases (including
SELECT *over aliases on plain text/keyword fields) are unaffected — the new branch only triggers when the resolved target isnull, which previously always threw.Related Issues
Resolves #5535
Check List
--signoffor-s.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
Update: adopt the ErrorReport report-builder interface (#5266)
Per reviewer feedback, the fix now wraps the
SemanticCheckExceptionin anErrorReport(introduced in #5266) instead of throwing it directly. This attaches structured context as the error bubbles up:code = FIELD_NOT_FOUND,stage = ANALYZING, a location chain, thealias_field/alias_pathcontext, and a fix suggestion.Verified live on both endpoints (both HTTP 400, no regression for valid aliases):
_plugins/_ppl) renders the full structured error:{ "error": { "reason": "Alias field [source_alias] refers to unresolved path [source.keyword].", "code": "FIELD_NOT_FOUND", "suggestion": "The alias path must point to an existing field in the mapping; a text multi-field (e.g. \"source.keyword.keyword\") or a removed/renamed field is not a valid alias target.", "context": { "stage": "analyzing", "stage_description": "Parsing and validating the query", "alias_field": "source_alias", "alias_path": "source.keyword" }, "location": ["while preparing and validating the query plan", "while resolving alias fields in the index mapping"], "type": "SemanticCheckException" }, "status": 400 }_plugins/_sql) still returns a clear 400 —RestSqlActionunwrapsErrorReportto itsSemanticCheckExceptioncause for status classification. Note:JdbcResponseFormatterdoes not yet render theErrorReportstructure (it reportstype: "ErrorReport"with justdetails); teaching the JDBC formatter to render the structured fields is a reasonable follow-up but is intentionally out of scope here to keep this change focused.Tests: unit test asserts the
ErrorReportcode/stage/context/cause; a PPL IT inCalciteErrorReportStageITasserts the structuredFIELD_NOT_FOUNDerror; the SQL IT inQueryValidationITasserts the 400.